<?php

class Skjb_Image
{
    /**
     * Image types
     */
    const TYPE_PNG = 1;
    const TYPE_JPEG = 2;
    const TYPE_GIF = 3;
    const TYPE_BMP = 4;
    const TYPE_TGA = 5;
    
    /**
     * The full path to the input file
     * 
     * @var
     */
    protected $_path = null;
    
    /**
     * The image pointer
     * 
     * @var
     */
    public $pointer = null;
    
    /**
     * The image width
     * 
     * @var
     */
    protected $_width = null;
    
    /**
     * The image height
     * 
     * @var
     */
    protected $_height = null;
    
    /**
     * Instantiate
     * 
     * @return 
     */
    public function __construct()
    {
        $argCount = func_num_args();
        switch ($argCount) {
            case 1:
                $this->_path = func_get_arg(0);
                $this->pointer = @imagecreatefromstring(file_get_contents($this->_path));
                if ($this->pointer === false) {
                    throw new Zend_Exception('Invalid file');
                }
                break;
            case 2:
                $this->_width = func_get_arg(0);
                $this->_height = func_get_arg(1);
                $this->pointer = $this->_createTransparentCanvas($this->_width, $this->_height);
                break;
            default:
                throw new Zend_Exception('Invalid argument count');
        }
    }
    
    /**
     * Destroy the image if necessary
     * 
     * @return 
     */
    public function __destruct()
    {
        if ($this->pointer !== null) {
            imagedestroy($this->pointer);
        }
    }
    
    /**
     * Get read-only properties
     * 
     * @param object $name
     * @return 
     */
    public function __get($name)
    {
        switch ($name) {
            case 'width':
                if ($this->_width == null) {
                    $this->_width = imagesx($this->pointer);
                }
                return $this->_width;
                break;
            case 'height':
                if ($this->_height == null) {
                    $this->_height = imagesy($this->pointer);
                }
                return $this->_height;
                break;
        }
        throw new Zend_Exception('Undefined property "' . $name . '"');
    }
    
    /**
     * Returns a duplicate of the image
     * 
     * @return Skjb_Image
     */
    public function duplicate()
    {
        $out = new Skjb_Image($this->width, $this->height);
        imagecopy($out->pointer, $this->pointer, 0, 0, 0, 0, $this->width, $this->height);
        return $out;
    }
    
    /**
     * Resizes the image to the given dimentions
     * 
     * @param int $width The desired width
     * @param int $height The desired height
     * @param boolean $proportional [optional] Whether to proportionally resize or not
     * @param boolean $overflow [optional] If set to true the longest size will be proportionate but larger than $width
     * @return Skjb_Image
     */
    public function resize($width, $height, $proportional = true, $overflow = false)
    {
        if ($this->width != $width || $this->height != $height) {
            if ($proportional) {
                $widthRatio = $width / $this->width;
                $heightRatio = $height / $this->height;
                if ($overflow) { // Resize to the largest side
                    if ($widthRatio > $heightRatio) {
                        $ratio = $widthRatio;
                    } else {
                        $ratio = $heightRatio;
                    }
                } else { // Resize to the smallest side
                    if ($widthRatio < $heightRatio) {
                        $ratio = $widthRatio;
                    } else {
                        $ratio = $heightRatio;
                    }
                }
                $outWidth = round($this->width * $ratio);
                $outHeight = round($this->height * $ratio);
            } else {
                $outWidth = $width;
                $outHeight = $height;
            }
            
            $out = $this->_createTransparentCanvas($outWidth, $outHeight);
            imagecopyresampled($out, $this->pointer, 0, 0, 0, 0, $outWidth, $outHeight, $this->width, $this->height);
            
            imagedestroy($this->pointer);
            $this->pointer = $out;
            
            $this->_width = $outWidth;
            $this->_height = $outHeight;
        }
        
        return $this;
    }
    
    /**
     * Crop the image to the given dimensions
     * 
     * @param object $x
     * @param object $y
     * @param object $width
     * @param object $height
     * @return 
     */
    public function crop($x, $y, $width, $height)
    {
        $out = $this->_createTransparentCanvas($width, $height);
        imagecopyresampled($out, $this->pointer, 0, 0, $x, $y, $width, $height, $width, $height);
        
        imagedestroy($this->pointer);
        $this->pointer = $out;

        $this->_width = $width;
        $this->_height = $height;
    }
    
    /**
     * Creates a completely transparent canvas
     * 
     * @param int $width
     * @param int $height
     * @return resource
     */
    protected function _createTransparentCanvas($width, $height)
    {
        $out = imagecreatetruecolor($width, $height);
        imagesavealpha($out, true);
        $transparentColour = imagecolorallocatealpha($out, 0, 0, 0, 127);
        imagefill($out, 0, 0, $transparentColour);
        return $out;
    }
    
    /**
     * Crops or expands the canvas the the specified size
     * 
     * @param int $width
     * @param int $height
     * @return Skjb_Image
     */
    public function resizeCanvas($width, $height)
    {
        if ($this->width != $width || $this->height != $height) {
            $outX = round(($width - $this->width) / 2);
            $outY = round(($height - $this->height) / 2);
            
            $out = $this->_createTransparentCanvas($width, $height);
            imagecopyresampled($out, $this->pointer, $outX, $outY, 0, 0, $this->width, $this->height, $this->width, $this->height);
            
            imagedestroy($this->pointer);
            $this->pointer = $out;
    
            $this->_width = $width;
            $this->_height = $height;
        }
        
        return $this;
    }
    
    /**
     * Overlays the given image
     * 
     * @param Skjb_Image $image
     * @param int $x [optional]
     * @param int $y [optional]
     * @return 
     */
    public function overlay($image, $x = 0, $y = 0)
    {
        imagecopyresampled($this->pointer, $image->pointer, $x, $y, 0, 0, $image->width, $image->height, $image->width, $image->height);
    }
    
    /**
     * Saves the image to a file, automatically determines the output type from the extension
     * 
     * @param string $file [optional] The file to save to, if ommitted the input path is used
     * @param int $quality [optional] The quality for JPEG compression
     * @return void
     */
    public function save($file = null, $quality = null)
    {
        if ($file == null) {
            $file = $this->_path;
        }
        $type = self::getType($file);
        switch ($type) {
            case self::TYPE_PNG:
                imagepng($this->pointer, $file);
                break;
            case self::TYPE_JPEG:
                if ($quality == null) {
                    imagejpeg($this->pointer, $file);
                } else {
                    imagejpeg($this->pointer, $file, $quality);
                }
                break;
            case self::TYPE_GIF:
                imagegif($this->pointer, $file);
                break;
        }
    }
    
    /**
     * Echos out the image
     * 
     * @param int $type
     * @param int $quality [optional]
     * @return 
     */
    public function output($type, $quality = null)
    {
        switch ($type) {
            case self::TYPE_PNG:
                imagepng($this->pointer);
                break;
            case self::TYPE_JPEG:
                if ($quality == null) {
                    imagejpeg($this->pointer);
                } else {
                    imagejpeg($this->pointer, null, $quality);
                }
                break;
            case self::TYPE_GIF:
                imagegif($this->pointer);
                break;
        }
    }
    
    /**
     * Gets the image type for the given filename
     * 
     * @param string $file
     * @return int
     */
    public static function getType($file)
    {
        $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
        switch ($extension) {
            case 'png':
                return self::TYPE_PNG;
            case 'jpg':
            case 'jpeg':
                return self::TYPE_JPEG;
            case 'gif':
                return self::TYPE_GIF;
            case 'bmp':
                return self::TYPE_BMP;
            case 'tga':
                return self::TYPE_TGA;
        }
        throw new Zend_Exception('Unknown file type');
    }
    
    /**
     * Utility to create a new object from the given filename
     * 
     * @param object $path
     * @return 
     */
    public static function load($path)
    {
        return new self($path);
    }
    
    /**
     * Utility to create a new object
     * 
     * @param object $width
     * @param object $height
     * @return 
     */
    public static function create($width, $height)
    {
        return new self($width, $height);
    }
    
    /**
     * Load image from a bitmap file
     * 
     * @param object $path
     * @return 
     */
    public function loadBitmap($path)
    {
        // Load the image into a string
        $file = fopen($path, 'rb');
        $read = fread($file,10);
        while (!feof($file) && ($read <> '')) {
            $read .= fread($file,1024);
        }
        
        $temp = unpack('H*', $read);
        $hex = $temp[1];
        $header = substr($hex, 0, 108);
        
        // Process the header
        // Structure: http://www.fastgraph.com/help/bmp_header_format.html
        if (substr($header, 0, 4) == '424d') {
            // Cut it in parts of 2 bytes
            $header_parts = str_split($header, 2);
            
            // Get the width: 4 bytes
            $width = hexdec($header_parts[19]. $header_parts[18]);
            
            // Get the height: 4 bytes
            $height = hexdec($header_parts[23]. $header_parts[22]);
            
            // Unset the header params
            unset($header_parts);
        }
        
        //    Define starting X and Y
        $x = 0;
        $y = 1;
        
        // Create newimage
        $image = imagecreatetruecolor($width,$height);
        
        // Grab the body from the image
        $body = substr($hex, 108);
        
        // Calculate if padding at the end-line is needed
        // Divided by two to keep overview.
        // 1 byte = 2 HEX-chars
        $body_size = strlen($body) / 2;
        $header_size = $width * $height;
        
        // Use end-line padding? Only when needed
        $usePadding = ($body_size > ($header_size * 3) + 4);
        
        // Using a for-loop with index-calculation instead of str_split to avoid large memory consumption
        // Calculate the next DWORD-position in the body
        for ($i = 0; $i < $body_size; $i += 3) {
            // Calculate line-ending and padding
            if ($x >= $width) {
                // If padding needed, ignore image-padding
                // Shift i to the ending of the current 32-bit-block
                if ($usePadding) {
                    $i += $width % 4;
                }
                
                // Reset horizontal position
                $x = 0;
                
                // Raise the height-position (bottom-up)
                $y++;
                
                // Reached the image-height? Break the for-loop
                if ($y > $height) {
                    break;
                }
            }
            
            // Calculation of the RGB-pixel (defined as BGR in image-data)
            // Define $i_pos as absolute position in the body
            $i_pos = $i * 2;
            $r = hexdec($body[$i_pos + 4] . $body[$i_pos + 5]);
            $g = hexdec($body[$i_pos + 2] . $body[$i_pos + 3]);
            $b = hexdec($body[$i_pos] . $body[$i_pos + 1]);
            
            // Calculate and draw the pixel
            $color = imagecolorallocate($image, $r, $g, $b);
            imagesetpixel($image, $x, $height - $y, $color);
            
            // Raise the horizontal position
            $x++;
        }
       
        // Unset the body / free the memory
        unset($body);
       
        // Return image-object
        return $image;
    }
    
    /**
     * Load image from a TGA file
     * 
     * @param object $filename
     * @return 
     */
    public function loadTarga($filename)
    {
        $handle = fopen($filename, 'rb');
        $data = fread($handle, filesize($filename));
        fclose($handle);
       
        $pointer = 18;
        $x = 0;
        $y = 0;
        $w = base_convert(bin2hex(strrev(substr($data, 12, 2))), 16, 10);
        $h = base_convert(bin2hex(strrev(substr($data, 14, 2))), 16, 10);
        $img = imagecreatetruecolor($w, $h);
    
        while ($pointer < strlen($data)) {
            imagesetpixel ($img, $x, $y, base_convert(bin2hex(strrev(substr($data, $pointer, 3))), 16, 10));
            $x++;
    
            if ($x == $w){
                $y++;
                $x = 0;
            }
    
            $pointer += 3;
        }
       
        return $img;
    }
}